/*******************************************************************************
 * Copyright (c) 2018, 2021 IBM Corp. and others
 *
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which accompanies this
 * distribution and is available at https://www.eclipse.org/legal/epl-2.0/
 * or the Apache License, Version 2.0 which accompanies this distribution and
 * is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * This Source Code may also be made available under the following
 * Secondary Licenses when the conditions for such availability set
 * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU
 * General Public License, version 2 with the GNU Classpath
 * Exception [1] and GNU General Public License, version 2 with the
 * OpenJDK Assembly Exception [2].
 *
 * [1] https://www.gnu.org/software/classpath/license.html
 * [2] http://openjdk.java.net/legal/assembly-exception.html
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception
 *******************************************************************************/
package org.openj9.test.lworld;

import org.objectweb.asm.*;

import static org.objectweb.asm.Opcodes.*;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;

public class ValueTypeGenerator extends ClassLoader {
	private static ValueTypeGenerator generator;
	
	private static boolean DEBUG = false;
	
	/* workaround till the new ASM is released */
	public static final int DEFAULTVALUE = 203;
	public static final int WITHFIELD = 204;
	private static final int ACC_VALUE_TYPE = 0x100;
	
	static {
		generator = new ValueTypeGenerator();
	}

	private static class ClassConfiguration {
		private String name;
		private String[] fields;
		private String nestHost;
		private boolean isVerifiable;
		private boolean isReference;

		/**
		 * @see setAccessedContainer
		 */
		private ClassConfiguration accessedContainer;

		/**
		 * @see setValueClassUsedInCode
		 */
		private ClassConfiguration valueClassUsedInCode;

		public ClassConfiguration(String name) {
			this.name = name;
		}

		public ClassConfiguration(String name, String[] fields) {
			this.name = name;
			this.fields = fields;
		}

		public String getName() {
			return name;
		}

		public String[] getFields() {
			return fields;
		}

		public String getNestHost() {
			return nestHost;
		}

		public void setNestHost(String nestHost) {
			this.nestHost = nestHost;
		}

		public void setIsReference(boolean isReference) {
			this.isReference = isReference;
		}

		public boolean isReference() {
			return isReference;
		}

		public void setIsVerifiable(boolean isVerifiable) {
			this.isVerifiable = isVerifiable;
		}

		public boolean isVerifiable() {
			return this.isVerifiable;
		}

		/**
		 * This method specifies a reference class - whose fields are expected to be of
		 * value types - an instance of which will be an argument to the
		 * {@code testUnresolvedValueTypePutField} and {@code testUnresolvedValueTypeGetField}
		 * methods that will be generated for the current class.  Those methods will perform
		 * {@code PUTFIELD} or {@code GETFIELD} operations, respectively, on the fields of
		 * the {@code accessedContainer} instance.
		 *
		 * The intention is to test delaying resolution of the fields and their types,
		 * particularly its effect on code generated by the JIT compiler.
		 */
		public void setAccessedContainer(ClassConfiguration accessedContainer) {
			this.accessedContainer = accessedContainer;
		}

		/**
		 * @see setAccessedContainer
		 */
		public ClassConfiguration getAccessedContainer() {
			return accessedContainer;
		}

		/**
		 * This method specifies a value type class that will be used in code generated
		 * for the {@code testUnresolvedValueTypeDefaultValue} and
		 * {@code testUnresolvedValueTypeWithField} methods of the current class.
		 * The former will conditionally perform a {@code DEFAULTVALUE} operation on the
		 * value type and the latter will conditionally perform a series of
		 * {@code WITHFIELD} operations on the fields of an instance of the value type.
		 * The instance should be passed to {@code testUnresolvedValueTypeWithField} via an
		 * argument of type {@link java.lang.Object}.
		 *
		 * The value type class will be declared to be a {@code NestMember} of the current
		 * class, and must in turn declare the current class to be its {@code NestHost}.
		 *
		 * The intention is to test delayed resolution of the value type class, particularly
		 * its effect on code generated by the JIT compiler.
		 */
		public void setValueClassUsedInCode(ClassConfiguration valueClassUsedInCode) {
			this.valueClassUsedInCode = valueClassUsedInCode;
		}

		/**
		 * @see setValueClassUsedInCode
		 */
		public ClassConfiguration getValueClassUsedInCode() {
			return valueClassUsedInCode;
		}
	}

	private static byte[] generateClass(ClassConfiguration config) {
		String className = config.getName();
		String[] fields = config.getFields();

		String nestHost = config.getNestHost();

		ClassConfiguration valueClassConfig = config.getValueClassUsedInCode();
		String valueUsedInCode = (valueClassConfig != null) ? valueClassConfig.getName() : null;
		String[] valueFields = (valueClassConfig != null) ? valueClassConfig.getFields() : null;

		ClassConfiguration containerClassConfig = config.getAccessedContainer();
		String containerUsedInCode = (containerClassConfig != null) ? containerClassConfig.getName() : null;
		String[] containerFields = (containerClassConfig != null) ? containerClassConfig.getFields() : null;

		boolean isVerifiable = config.isVerifiable();
		boolean isRef = config.isReference();

		ClassWriter cw = new ClassWriter(0);
		FieldVisitor fv;
		MethodVisitor mv;
		String classFileName = className + ".class";

		if (isRef) {
			cw.visit(56, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, className, null, "java/lang/Object", null);
		} else {
			cw.visit(56, ACC_PUBLIC + ACC_FINAL + ACC_SUPER + ACC_VALUE_TYPE, className, null, "java/lang/Object", null);
		}

		cw.visitSource(className + ".java", null);

		if (nestHost != null) {
			cw.visitNestHost(nestHost);
		}

		if (valueUsedInCode != null) {
			cw.visitNestMember(valueUsedInCode);
		}
		
		int makeMaxLocal = 0;
		String makeValueSig = "";
		String makeValueGenericSig = "";
		for (String s : fields) {
			String nameAndSigValue[] = s.split(":");
			final int fieldModifiers;
			if (isRef) {
				fieldModifiers = ACC_PUBLIC;
			} else {
				if ((nameAndSigValue.length > 2) && nameAndSigValue[2].equals("static")) {
					fieldModifiers = ACC_PUBLIC + ACC_STATIC;
				} else {
					fieldModifiers = ACC_PUBLIC + ACC_FINAL;
				}
			}
			fv = cw.visitField(fieldModifiers, nameAndSigValue[0], nameAndSigValue[1], null, null);
			fv.visitEnd();
			if ((nameAndSigValue.length <= 2) || !nameAndSigValue[2].equals("static")) {
				makeValueSig += nameAndSigValue[1];
				makeValueGenericSig += "Ljava/lang/Object;";
				if (nameAndSigValue[1].equals("J") || nameAndSigValue[1].equals("D")) {
					makeMaxLocal += 2;
				} else {
					makeMaxLocal += 1;
				}
			}

			generateFieldMethods(cw, nameAndSigValue, className, isVerifiable, isRef);
		}
		
		if (isRef) {
			initHelper(cw);
			makeRef(cw, className, makeValueSig, makeValueGenericSig, fields, makeMaxLocal);
			makeRefDefaultValue(cw, className, makeValueSig, fields, makeMaxLocal);
			if (!isVerifiable) {
				makeGeneric(cw, className, "makeRefGeneric", "makeRef", makeValueSig, makeValueGenericSig, fields, makeMaxLocal, isRef);
				/* makeValue is invalid on ref: Included to test if runtime error is (correctly) thrown */
				/* makeValue(cw, className, makeValueSig, fields, makeMaxLocal); */
				makeValueTypeDefaultValue(cw, className, makeValueSig, fields, makeMaxLocal, isRef);
			}

			testWithFieldOnNonValueType(cw, className, fields);
			testWithFieldOnNull(cw, className, fields);
			testWithFieldOnNonExistentClass(cw, className, fields);
			testMonitorExitOnObject(cw, className, fields);
			testMonitorEnterAndExitWithRefType(cw, className, fields);
			testCheckCastRefClassOnNull(cw, className, fields);
			if (valueUsedInCode != null) {
				testUnresolvedValueTypeDefaultValue(cw, className, valueUsedInCode);
				if (valueFields != null) {
					testUnresolvedValueTypeWithField(cw, className, valueUsedInCode, valueFields);
				}
			}
			if (containerFields != null) {
				testUnresolvedValueTypePutField(cw, className, containerUsedInCode, containerFields);
				testUnresolvedValueTypeGetField(cw, className, containerUsedInCode, containerFields);
			}
		} else {
			makeValue(cw, className, makeValueSig, fields, makeMaxLocal);
			makeValueTypeDefaultValue(cw, className, makeValueSig, fields, makeMaxLocal, isRef);
			testCheckCastValueTypeOnNull(cw, className, fields);
			testCheckCastValueTypeOnNonNullType(cw, className, fields);
			if (!isVerifiable) {
				makeGeneric(cw, className, "makeValueGeneric", "makeValue", makeValueSig, makeValueGenericSig, fields, makeMaxLocal, isRef);
			}
		}
		test2DMultiANewArray(cw, className, isRef);
		testCheckCastOnInvalidQtype(cw);
		testCheckCastOnInvalidLtype(cw);
		addStaticSynchronizedMethods(cw);
		addSynchronizedMethods(cw);
		cw.visitEnd();
		
		byte[] bytes = cw.toByteArray();
		generateClassFile(classFileName, bytes);
		
		return bytes;
		
	}

	private static void addStaticSynchronizedMethods(ClassWriter cw) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC + ACC_SYNCHRONIZED, "staticSynchronizedMethodReturnInt", "()I", null, null);
		mv.visitCode();
		mv.visitInsn(ICONST_1);
		mv.visitInsn(IRETURN);
		mv.visitMaxs(1, 0);
		mv.visitEnd();
	}
	
	private static void addSynchronizedMethods(ClassWriter cw) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_SYNCHRONIZED, "synchronizedMethodReturnInt", "()I", null, null);
		mv.visitCode();
		mv.visitInsn(ICONST_1);
		mv.visitInsn(IRETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}
	
	private static void generateFieldMethods(ClassWriter cw, String[] nameAndSigValue, String className, boolean isVerifiable, boolean isRef) {
		if ((nameAndSigValue.length > 2) && nameAndSigValue[2].equals("static")) {
			generateSetterStatic(cw, nameAndSigValue, className);
			generateGetterStatic(cw, nameAndSigValue, className);
			if (!isVerifiable) {
				generateStaticSetterGeneric(cw, nameAndSigValue, className);
				generateStaticGetterGeneric(cw, nameAndSigValue, className);
			}
		} else {
			generateGetter(cw, nameAndSigValue, className);
			generateSetter(cw, nameAndSigValue, className);
			generateWither(cw, nameAndSigValue, className, isRef);
			if (!isVerifiable) {
				generateGetterGeneric(cw, nameAndSigValue, className);
				generateWitherGeneric(cw, nameAndSigValue, className, isRef);
				generateSetterGeneric(cw, nameAndSigValue, className);
			}
		}
	}
	
	private static void initHelper(ClassWriter cw) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
		mv.visitInsn(RETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}
	
	/*
	 * This function should only be called in the 
	 * TestWithFieldOnNonValueType test
	 */
	private static void testWithFieldOnNonValueType(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testWithFieldOnNonValueType", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, className);
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V", false);
		mv.visitLdcInsn(Long.valueOf(123L));
		mv.visitFieldInsn(WITHFIELD, className, "longField", "J");
		mv.visitInsn(ARETURN);
		mv.visitMaxs(3, 2);
		mv.visitEnd();
	}
	
	/*
	 * This function should only be called in the 
	 * TestWithFieldOnNull test
	 */
	private static void testWithFieldOnNull(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testWithFieldOnNull", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitInsn(ACONST_NULL);
		mv.visitLdcInsn(Long.valueOf(123L));
		mv.visitFieldInsn(WITHFIELD, className, "longField", "J");
		mv.visitInsn(ARETURN);
		mv.visitMaxs(3, 2);
		mv.visitEnd();
	}
	
	/*
	 * This function should only be called in the 
	 * TestWithFieldOnNonExistentClass test
	 */
	private static void testWithFieldOnNonExistentClass(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testWithFieldOnNonExistentClass", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, className);
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V", false);
		mv.visitLdcInsn(Long.valueOf(123L));
		mv.visitFieldInsn(WITHFIELD, "NonExistentClass", "longField", "J");
		mv.visitInsn(ARETURN);
		mv.visitMaxs(3, 2);
		mv.visitEnd();
	}

	 /* 
	  * This function should only be called in the
	  * TestMonitorExitOnValueType test and
	  * TestMonitorExitWithRefType test
	  */
	private static void testMonitorExitOnObject(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testMonitorExitOnObject", "(Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitInsn(MONITOREXIT);
		mv.visitInsn(RETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}

	 /* 
	  * This function should only be called in the
	  * TestMonitorEnterAndExitWithRefType test
	  */
	private static void testMonitorEnterAndExitWithRefType(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testMonitorEnterAndExitWithRefType", "(Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitInsn(DUP);
		mv.visitInsn(MONITORENTER);
		mv.visitInsn(MONITOREXIT);
		mv.visitInsn(RETURN);
		mv.visitMaxs(2,1);
		mv.visitEnd();
	}

	private static void makeValue(ClassWriter cw, String valueName, String makeValueSig, String[] fields, int makeMaxLocal) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "makeValue", "(" + makeValueSig + ")" + getSigFromSimpleName(valueName, false), null, null);
		mv.visitCode();
		mv.visitTypeInsn(DEFAULTVALUE, getSigFromSimpleName(valueName, false));
		for (int i = 0, count = 0; i <  fields.length; i++) {
			String nameAndSig[] = fields[i].split(":");
			if ((nameAndSig.length < 3) ||  !(nameAndSig[2].equals("static"))) {
				switch (nameAndSig[1]) {
				case "D":
					mv.visitVarInsn(DLOAD, count);
					doubleDetected = true;
					count += 2;
					break;
				case "I":
				case "Z":
				case "B":
				case "C":
				case "S":
					mv.visitVarInsn(ILOAD, count);
					count++;
					break;
				case "F":
					mv.visitVarInsn(FLOAD, count);
					count++;
					break;
				case "J":
					mv.visitVarInsn(LLOAD, count);
					doubleDetected = true;
					count += 2;
					break;
				default:
					mv.visitVarInsn(ALOAD, count);
					count++;
					break;
				}
				mv.visitFieldInsn(WITHFIELD, valueName, nameAndSig[0], nameAndSig[1]);
			}
		}
		mv.visitInsn(ARETURN);
		int maxStack = (doubleDetected) ? 3 : 2;
		mv.visitMaxs(maxStack, makeMaxLocal);
		mv.visitEnd();
	}
	
	private static void makeValueTypeDefaultValue(ClassWriter cw, String valueName, String makeValueSig, String[] fields, int makeMaxLocal, boolean isRef) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, "makeValueTypeDefaultValue", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitTypeInsn(DEFAULTVALUE, getSigFromSimpleName(valueName, isRef));
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 0);
		mv.visitEnd();
	}

	private static void testCheckCastValueTypeOnNonNullType(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, "testCheckCastValueTypeOnNonNullType", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitTypeInsn(DEFAULTVALUE, getSigFromSimpleName(className, false));
		mv.visitTypeInsn(CHECKCAST, className);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 2);
		mv.visitEnd();
	}

	private static void testCheckCastValueTypeOnNull(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testCheckCastValueTypeOnNull", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitInsn(ACONST_NULL);
		mv.visitTypeInsn(CHECKCAST, getSigFromSimpleName(className, false));
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 2);
		mv.visitEnd();
	}
	
	private static void testCheckCastOnInvalidQtype(ClassWriter cw) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testCheckCastOnInvalidQtype", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitInsn(ACONST_NULL);
		mv.visitTypeInsn(CHECKCAST, "QClassDoesNotExist;");
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 2);
		mv.visitEnd();
	}
	
	private static void testCheckCastOnInvalidLtype(ClassWriter cw) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testCheckCastOnInvalidLtype", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitInsn(ACONST_NULL);
		mv.visitTypeInsn(CHECKCAST, "ClassDoesNotExist");
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 2);
		mv.visitEnd();
	}

	private static void testCheckCastRefClassOnNull(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testCheckCastRefClassOnNull", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, className);
		mv.visitTypeInsn(CHECKCAST, className);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 2);
		mv.visitEnd();
	}

	private static void testUnresolvedValueTypeDefaultValue(ClassWriter cw, String className, String valueUsedInCode) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testUnresolvedValueTypeDefaultValue", "(I)Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ILOAD, 0);
		Label falseLabel = new Label();
		Label endLabel = new Label();
		mv.visitJumpInsn(IFEQ, falseLabel);
		mv.visitTypeInsn(DEFAULTVALUE, getSigFromSimpleName(valueUsedInCode, false));
		mv.visitJumpInsn(GOTO, endLabel);
		mv.visitLabel(falseLabel);
		mv.visitInsn(ACONST_NULL);
		mv.visitLabel(endLabel);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}

	private static void testUnresolvedValueTypeWithField(ClassWriter cw, String className, String valueUsedInCode, String[] valueFields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testUnresolvedValueTypeWithField", "(ILjava/lang/Object;)Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ILOAD, 0);
		Label falseLabel = new Label();
		Label endLabel = new Label();
		mv.visitJumpInsn(IFEQ, falseLabel);
		mv.visitVarInsn(ALOAD, 1);
		mv.visitTypeInsn(CHECKCAST, valueUsedInCode);
		for (int i = 0; i < valueFields.length; i++) {
			String[] nameAndSig = valueFields[i].split(":");
			mv.visitLdcInsn(Integer.valueOf(i+1));
			mv.visitFieldInsn(WITHFIELD, valueUsedInCode, nameAndSig[0], nameAndSig[1]);
		}
		mv.visitJumpInsn(GOTO, endLabel);
		mv.visitLabel(falseLabel);
		mv.visitInsn(ACONST_NULL);
		mv.visitLabel(endLabel);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(2, 2);
		mv.visitEnd();
	}

	private static void testUnresolvedValueTypeGetField(ClassWriter cw, String className, String containerClassName, String[] containerFields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testUnresolvedValueTypeGetField", "(IL"+containerClassName+";)Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ILOAD, 0);
		int fieldCount = containerFields.length;
		Label endLabel = new Label();
		Label defaultLabel = new Label();
		Label[] caseLabels = new Label[fieldCount];
		for (int i = 0; i < fieldCount; i++) {
			caseLabels[i] = new Label();
		}
		mv.visitTableSwitchInsn(0, fieldCount-1, defaultLabel, caseLabels);
		for (int i = 0; i < fieldCount; i++) {
			String nameAndSigValue[] = containerFields[i].split(":");
			mv.visitLabel(caseLabels[i]);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitFieldInsn(GETFIELD, containerClassName, nameAndSigValue[0], nameAndSigValue[1]);
			mv.visitJumpInsn(GOTO, endLabel);
		}
		mv.visitLabel(defaultLabel);
		mv.visitInsn(ACONST_NULL);
		mv.visitLabel(endLabel);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 2);
		mv.visitEnd();
	}

	private static void testUnresolvedValueTypePutField(ClassWriter cw, String className, String containerClassName, String[] containerFields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testUnresolvedValueTypePutField", "(IL"+containerClassName+";Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ILOAD, 0);
		int fieldCount = containerFields.length;
		Label endLabel = new Label();
		Label defaultLabel = new Label();
		Label[] caseLabels = new Label[fieldCount];
		for (int i = 0; i < fieldCount; i++) {
			caseLabels[i] = new Label();
		}
		mv.visitTableSwitchInsn(0, fieldCount-1, defaultLabel, caseLabels);
		for (int i = 0; i < fieldCount; i++) {
			String nameAndSigValue[] = containerFields[i].split(":");
			mv.visitLabel(caseLabels[i]);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitVarInsn(ALOAD, 2);
			mv.visitTypeInsn(CHECKCAST, nameAndSigValue[1]);
			mv.visitFieldInsn(PUTFIELD, containerClassName, nameAndSigValue[0], nameAndSigValue[1]);
			mv.visitJumpInsn(GOTO, endLabel);
		}
		mv.visitLabel(defaultLabel);
		mv.visitLabel(endLabel);
		mv.visitInsn(RETURN);
		mv.visitMaxs(2, 3);
		mv.visitEnd();
	}

	private static void makeRefDefaultValue(ClassWriter cw, String className, String makeValueSig, String[] fields, int makeMaxLocal) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, "makeRefDefaultValue", "()L" + className + ";", null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, className);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 0);
		mv.visitEnd();
	}

	private static void makeGeneric(ClassWriter cw, String className, String methodName, String specificMethodName, String makeValueSig, String makeValueGenericSig, String[] fields, int makeMaxLocal, boolean isRef) {		
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, methodName, "(" + makeValueGenericSig + ")Ljava/lang/Object;", null, new String[] {"java/lang/Exception"});
		mv.visitCode();
		for (int i = 0; i <  fields.length; i++) {
			String nameAndSigValue[] = fields[i].split(":");
			if ((nameAndSigValue.length < 3) ||  !(nameAndSigValue[2].equals("static"))) {
				mv.visitVarInsn(ALOAD, i);
				switch (nameAndSigValue[1]) {
				case "D":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
					break;
				case "I":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
					break;
				case "Z":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
					break;
				case "B":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
					break;
				case "C":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
					break;
				case "S":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
					break;
				case "F":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
					break;
				case "J":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
					break;
				default:
					String signature = nameAndSigValue[1];
					
					if ('L' == signature.charAt(0)) {
						signature = signature.substring(1, signature.length() - 1);
					}
					mv.visitTypeInsn(CHECKCAST, signature);
					break;
				}
			}
		}
		mv.visitMethodInsn(INVOKESTATIC, className, specificMethodName, "(" + makeValueSig + ")" + getSigFromSimpleName(className, isRef), false);
		mv.visitInsn(ARETURN);
		int maxStack = makeMaxLocal;
		if (0 == maxStack) {
			maxStack += 1;
		}
		mv.visitMaxs(maxStack, makeMaxLocal);
		mv.visitEnd();
	}
	
	private static void makeRef(ClassWriter cw, String className, String makeValueSig, String makeValueGenericSig, String[]fields, int makeMaxLocal) {
		boolean doubleDetected = false;
		int makeRefArgsAndLocals = makeMaxLocal + 1; //extra slot is to store the ref being created

		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, "makeRef", "(" + makeValueSig + ")" + getSigFromSimpleName(className, true), null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, className);
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V", false);
		mv.visitVarInsn(ASTORE, makeMaxLocal);
		for (int i = 0, count = 0; i < fields.length; i++) {
			mv.visitVarInsn(ALOAD, makeMaxLocal);
			String nameAndSigValue[] = fields[i].split(":");
			if ((nameAndSigValue.length < 3) ||  !(nameAndSigValue[2].equals("static"))) {
				switch (nameAndSigValue[1]) {
				case "D":
					mv.visitVarInsn(DLOAD, count);
					doubleDetected = true;
					count += 2;
					break;
				case "I":
				case "Z":
				case "B":
				case "C":
				case "S":
					mv.visitVarInsn(ILOAD, count);
					count++;
					break;
				case "F":
					mv.visitVarInsn(FLOAD, count);
					count++;
					break;
				case "J":
					mv.visitVarInsn(LLOAD, count);
					doubleDetected = true;
					count += 2;
					break;
				default:
					mv.visitVarInsn(ALOAD, count);
					count++;
					break;
				}
				mv.visitFieldInsn(PUTFIELD, className, nameAndSigValue[0], nameAndSigValue[1]);
			}
		}
		mv.visitVarInsn(ALOAD, makeMaxLocal);
		mv.visitInsn(ARETURN);
		
		int maxStack = (doubleDetected) ? 3 : 2;
		mv.visitMaxs(maxStack, makeRefArgsAndLocals);
		mv.visitEnd();
	}

	private static void generateSetter(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "set" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitVarInsn(DLOAD, 1);
			doubleDetected = true;
			break;
		case "I":
		case "Z":
		case "B":
		case "C":
		case "S":
			mv.visitVarInsn(ILOAD, 1);
			break;
		case "F":
			mv.visitVarInsn(FLOAD, 1);
			break;
		case "J":
			mv.visitVarInsn(LLOAD, 1);
			doubleDetected = true;
			break;
		default:
			mv.visitVarInsn(ALOAD, 1);
			break;
		}
		mv.visitFieldInsn(PUTFIELD, className, nameAndSigValue[0], nameAndSigValue[1]);
		mv.visitInsn(RETURN);
		int maxStackAndLocals = (doubleDetected ? 3 : 2);
		mv.visitMaxs(maxStackAndLocals, maxStackAndLocals);
		mv.visitEnd();
	}
	
	private static void generateSetterStatic(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod((ACC_PUBLIC | ACC_STATIC), "setStatic" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")V", null, null);
		mv.visitCode();
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitVarInsn(DLOAD, 0);
			doubleDetected = true;
			break;
		case "I":
		case "Z":
		case "B":
		case "C":
		case "S":
			mv.visitVarInsn(ILOAD, 0);
			break;
		case "F":
			mv.visitVarInsn(FLOAD, 0);
			break;
		case "J":
			mv.visitVarInsn(LLOAD, 0);
			doubleDetected = true;
			break;
		default:
			mv.visitVarInsn(ALOAD, 0);
			break;
		}
		mv.visitFieldInsn(PUTSTATIC, className, nameAndSigValue[0], nameAndSigValue[1]);
		mv.visitInsn(RETURN);
		int maxStackAndLocals = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStackAndLocals, maxStackAndLocals);
		mv.visitEnd();
	}

	private static void generateStaticSetterGeneric(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "setStaticGeneric" + nameAndSigValue[0], "(Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
			doubleDetected = true;
			break;
		case "I":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
		case "Z":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
		case "B":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
		case "C":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
		case "S":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
			break;
		case "F":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
			break;
		case "J":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
			doubleDetected = true;
			break;
		default:
			break;
		}
		mv.visitMethodInsn(INVOKESTATIC, className, "setStatic" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")V", false);
		mv.visitInsn(RETURN);
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 1);
		mv.visitEnd();
	}

	private static void generateSetterGeneric(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "setGeneric" + nameAndSigValue[0], "(Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitVarInsn(ALOAD, 1);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
			doubleDetected = true;
			break;
		case "I":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
		case "Z":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
		case "B":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
		case "C":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
		case "S":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
			break;
		case "F":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
			break;
		case "J":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
			doubleDetected = true;
			break;
		default:
			break;
		}
		mv.visitMethodInsn(INVOKEVIRTUAL, className, "set" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")V", false);
		mv.visitInsn(RETURN);
		int maxStack = (doubleDetected ? 3 : 2);
		mv.visitMaxs(maxStack, 2);
		mv.visitEnd();
	}

	private static void generateWither(ClassWriter cw, String[] nameAndSigValue, String className, boolean isRef) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "with" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")" + getSigFromSimpleName(className, isRef), null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitVarInsn(DLOAD, 1);
			doubleDetected = true;
			break;
		case "I":
		case "Z":
		case "B":
		case "C":
		case "S":
			mv.visitVarInsn(ILOAD, 1);
			break;
		case "F":
			mv.visitVarInsn(FLOAD, 1);
			break;
		case "J":
			mv.visitVarInsn(LLOAD, 1);
			doubleDetected = true;
			break;
		default:
			mv.visitVarInsn(ALOAD, 1);
			break;
		}
		mv.visitFieldInsn(WITHFIELD, className, nameAndSigValue[0], nameAndSigValue[1]);
		mv.visitInsn(ARETURN);
		int maxStackAndLocals = (doubleDetected ? 3 : 2);
		mv.visitMaxs(maxStackAndLocals, maxStackAndLocals);
		mv.visitEnd();
	}
		
	private static void generateWitherGeneric(ClassWriter cw, String[] nameAndSigValue, String className, boolean isRef) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "withGeneric" + nameAndSigValue[0], "(Ljava/lang/Object;)Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitVarInsn(ALOAD, 1);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
			doubleDetected = true;
			break;
		case "I":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
			break;
		case "Z":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
			break;
		case "B":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
			break;
		case "C":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
			break;
		case "S":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
			break;
		case "F":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
			break;
		case "J":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
			doubleDetected = true;
			break;
		default:
			break;
		}

		mv.visitMethodInsn(INVOKEVIRTUAL, className, "with" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")" +getSigFromSimpleName(className, isRef), false);
		mv.visitInsn(ARETURN);
		int maxStack = (doubleDetected ? 3 : 2);
		mv.visitMaxs(maxStack, 2);
		mv.visitEnd();
	}
	
	private static void generateGetter(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "get" + nameAndSigValue[0], "()" + nameAndSigValue[1], null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitFieldInsn(GETFIELD, className, nameAndSigValue[0], nameAndSigValue[1]);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitInsn(DRETURN);
			doubleDetected = true;
			break;
		case "I":
		case "Z":
		case "B":
		case "C":
		case "S":
			mv.visitInsn(IRETURN);
			break;
		case "F":
			mv.visitInsn(FRETURN);
			break;
		case "J":
			mv.visitInsn(LRETURN);
			doubleDetected = true;
			break;
		default:
			mv.visitInsn(ARETURN);
			break;
		}
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 1);
		mv.visitEnd();
	}
	
	private static void generateGetterStatic(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod((ACC_PUBLIC | ACC_STATIC), "getStatic" + nameAndSigValue[0], "()" + nameAndSigValue[1], null, null);
		mv.visitCode();
		mv.visitFieldInsn(GETSTATIC, className, nameAndSigValue[0], nameAndSigValue[1]);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitInsn(DRETURN);
			doubleDetected = true;
			break;
		case "I":
		case "Z":
		case "B":
		case "C":
		case "S":
			mv.visitInsn(IRETURN);
			break;
		case "F":
			mv.visitInsn(FRETURN);
			break;
		case "J":
			mv.visitInsn(LRETURN);
			doubleDetected = true;
			break;
		default:
			mv.visitInsn(ARETURN);
			break;
		}
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 0);
		mv.visitEnd();
	}

	private static void generateGetterGeneric(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getGeneric" + nameAndSigValue[0], "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKEVIRTUAL, className, "get" + nameAndSigValue[0], "()" + nameAndSigValue[1], false);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
			doubleDetected = true;
			break;
		case "I":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
			break;
		case "Z":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
			break;
		case "B":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
			break;
		case "C":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
			break;
		case "S":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
			break;
		case "F":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
			break;
		case "J":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
			doubleDetected = true;
			break;
		default:
			break;
		}

		mv.visitInsn(ARETURN);
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 1);
		mv.visitEnd();
	}

	private static void generateStaticGetterGeneric(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "getStaticGeneric" + nameAndSigValue[0], "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitMethodInsn(INVOKESTATIC, className, "getStatic" + nameAndSigValue[0], "()" + nameAndSigValue[1], false);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
			doubleDetected = true;
			break;
		case "I":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
			break;
		case "Z":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
			break;
		case "B":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
			break;
		case "C":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
			break;
		case "S":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
			break;
		case "F":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
			break;
		case "J":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
			doubleDetected = true;
			break;
		default:
			break;
		}

		mv.visitInsn(ARETURN);
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 0);
		mv.visitEnd();
	}
	
	private static String getSigFromSimpleName(String className, boolean isRef) {
		String classNameSignature = null;
		if (isRef) {
			classNameSignature = "L" + className + ";";
		} else {
			classNameSignature = "Q" + className + ";";
		}
		return classNameSignature;
	}
	
	public static void generateClassFile(String name, byte[] bytes) {
		if (DEBUG) {
			try (FileOutputStream stream = new FileOutputStream(name)) {
				stream.write(bytes);
			}  catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static Class<?> generateValueClass(String name, String[] fields) throws Throwable {
		return generateValueClass(name, fields, null);
	}

	public static Class<?> generateValueClass(String name, String[] fields, String nestHost) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);

		if (nestHost != null) {
			classConfig.setNestHost(nestHost);
		}

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateVerifiableValueClass(String name, String[] fields) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		classConfig.setIsVerifiable(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateRefClass(String name, String[] fields) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		classConfig.setIsReference(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateRefClass(String name, String[] fields, String valueUsedInCode) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		ClassConfiguration valueClassConfig = new ClassConfiguration(valueUsedInCode);
		classConfig.setValueClassUsedInCode(valueClassConfig);
		classConfig.setIsReference(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateHostRefClass(String name, String[] fields, String valueUsedInCode, String[] valueFields) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		ClassConfiguration valueClassConfig = new ClassConfiguration(valueUsedInCode, valueFields);
		classConfig.setValueClassUsedInCode(valueClassConfig);
		classConfig.setIsReference(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateRefClass(String name, String[] fields, String containerClassName, String[] containerFields) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		ClassConfiguration containerClassConfig = new ClassConfiguration(containerClassName, containerFields);
		containerClassConfig.setIsReference(true);
		classConfig.setAccessedContainer(containerClassConfig);
		classConfig.setIsReference(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static void test2DMultiANewArray(ClassWriter cw, String className, boolean isRef) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "generate2DMultiANewArray", "(II)Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ILOAD, 0);
		mv.visitVarInsn(ILOAD, 1);
		mv.visitMultiANewArrayInsn("[[" + getSigFromSimpleName(className, isRef), 2);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(2, 2);
		mv.visitEnd();
	}
}
